REST Web Services oAuth2 Tutorial - Felix John COLIBRI. |
- abstract : Delphi DropBox Rest Service Client using the OAuth2 protocol. Implemented with the tRestClient Delphi components or Indy tIdHttp component. Get the DropBox token, list the files, download and upload files
- key words : Delphi Rest Client, DropBox; oAuth2 protocol, tRestClient tRestRequest tRestResponse, Indy tItHttp, OpenSSL
- hardware used : intel i3-8100, 3.6 gHz, 8GB memory, 128 G and 1 T hard disc
- software used : Windows 10 64 bit, Delphi Xe8
- scope : Delphi 1 to 7, 2006 to 2010, Xe, Seattle, Tokyo, Berlin, 10.3
- level : Delphi developer
- plan :
1 - DropBox App
I created a DropBox account and saved some photos there. I wanted to allow some friend to build a Delphi application for viewing those photos, whithout giving this friend full access to my DropBox account (sending him my DropBox User / Password).
This is where Oauth2 comes into play. This article will present - the Oauth2 teminology
- the Oauth2 flow
- how to create a DropBox App
- the Delphi DropBox application allowing to download / upload files to this
DropBox account
2 - The OAuth2 protocol 2.1 - The 3 parties Basically, you have 3 parties involved
- DropBox which allows anybody to create an account and save data on the Cloud and retrieve this data from anywhere. This is the SERVICE
- the person which creates this DropBox account. He is the Oauth2 (account)
OWNER, or sometimes called the "user"
- some other person, the CLIENT, might want to read the OWNER data
2.2 - OWNER creates an account and puts some data
At the beginning, the OWNER creates an account at the DropBox SERVICE: and then puts some data in his account
2.3 - OWNER creates a SERVICE APP To allow some third party, the CLIENT to read the data, OWNER creates a
"SERVICE App" (for instance a DropBox App). Meaning: he asks the SERVICE to prepare IDs that he will hand over to the CLIENT to start the OAUTH2 protocol. Using some SERVICE utility, he requests a "SERVICE App". The SERVICE generates
a Client_id, a Secret_id and asks for a redirection URL. So the OWNER must have an HTTP server (usually HTTPS) where SERVICE will be able to redirect the authorization requests. Suppose the url of this server is https//redir.html.
The OWNER then creates the "SERVICE App" After this operation, the OWNER account looks like this
CLIENT requests token Some CLIENT wants to read the OWNER data. To do so he will use a REST service
asking SERVICE for this data. He will be allowed to do so if he has a token. To get this token - the CLIENT asks the OWNER to send him (by phone, mail, email etc) the client_id and the redirect.html
- the client sends a REST request to the SERVICE to get the token
- on the SERVICE, this REST request is handled by an authentication server. This server uses the client_id to locate the "OWNER App". It checks that the redir.html matches the redir.html the OWNER provided. If this is the case,
the SERVICE sends an html Form to the OWNER https://redir.html, asking him whether he accepts this request
- the OWNER sees an HTML page and click "OK". This is sent back to the SERVER
- the SERVICE then generates a token and sends it back to the CLIENT
- with this token, the CLIENT can now performed any action the SERVICE allows. In our case, then CLIENT will request the data
- happy to oblige, the SERVICE sends back the data
A couple of points - as the diagram shows, the OWNER credential are never exchanged during this protocol. The purpose of OAUTH2 is to replace the OWNER userpassword with the token
- the token is generated by the SERVICE when required. Its lifetime depends on the SERVICE. For Dropbox, it lasts about 2 hours. After that the CLIENT has to travel this route again. In fact, there are refresh tokens, which allow
the CLIENT to use the token for a longer time span.
On the DropBox App page, the OWNER can create a new token by clicking the "Create Token" button. But the OWNER is not supposed to hand this token to
the CLIENT if he follows the Oauth2 protocol. However we can use it for debugging the CLIENT application, to avoid the tedious and long acceptation dialog (steps 6 to 9) - steps 6 to 9 are used to obtain this token. Steps 10 and 11 are then
repeated by the CLIENT as many time as he likes, and allow him to send the whole gammut of services the SERVICE offers. For DropBox, we can require the list of the files, download files, upload files etc
- when the OWNER creates the "SERVICE App", he has no idea of the application the CLIENT is going to use. It can be a Delphi application, the Delphi RestDebugger, a Java application. In fact any application able to send
client Rest requests and handle the Oauth2 protocol.
- for me, the name "client_id" is a misnomer: this ID is created by the SERVICE when the OWNER creates the "SERVICE App". I would have called this
"owner_id". The same goes for "SERVICE App" (like a DropbBox App). It is not an application at all, but a record with 4 informations (client_id, secret_id, redirection_url and token). The CLIENT will create or use some application
To sum it up: "OAUTH2 is a protocol that allows a user to grant limited access to their resources on one site, to another site, without having to expose their credentials". And
- the OWNER only sends the client_id and redirection_url to the CLIENT. Not his DropBox User / Password
- with those client_id / redirection_url, the CLIENT can ask a token, and then use this token to read from and write to the DropBox
3 - Creating the DropBox App First of all, we have to create a DropBox account. This is standard registration stuff.
Next, to be able to upload and download files, you must create a "DropBox App" | go to the Dropbox developer page https://www.dropbox.com/developers
and click "Create apps" | |
the app creation page is displayed | | select "Dropbox api" |
| the access selection buttons are displayed |
| select "Full Dropbox" name your app, like "delphi_boxer_app", check "I agree" and click "Create app" |
| the app settings page is displayed |
| save your key mshall8oznmrrcy click on "secret" to display and save your secret key p6j8ef54yyjy8du
type the URL of your redirection http://localhost/ and click "Add" click "generate" to generate your access token, and save it
X5SWPFqX3CAAAAAAAAAAL6TPIHZYgVSCIw4OPOgiIvibZTpMqNoobY5cXMSBxU0l |
Note - to delete a redirection url, select it's line and click the "x" at the right of the line
3.1 - Error | we could not retrieve the redirection page |
3.2 - Display your apps To visualize all your apps | in your browser, type the URL https://www.dropbox.com/developers/apps |
| all your apps are displayed |
When you are creating a new app, the top-right button "App console" also allows to display all your apps
4 - The DropBox API 4.1 - How to find the Api documentation
Most Rest Services are referenced on the Rest Api Directory. Right nowh they claim about 22.981 Apis. So typing "DropBox" and clicking "Search Apis" will display a DropBox link:
Clicking on the DropBox link we get
This link brings us to DropBox V1 documentation:
Now since I know that DropBox v1 should be replaced by DropBox v2, I found the correct DropBox V2 documentation:
On the right of the page are the links to the different commands. The
documentation for each command is quite complete, For example, to download a file: to get our token, we select
5 - The Delphi DropBox Rest Client 5.1 - Delphi RestDemo This project started with the Delphi RestDemo.dpr. The project contains Rest
Clients able to communicate with Delphi Praxis, Google Tasks, Facebook, Twine, Foursquare, DropBox and FitBit. I simply extracted the DropBox part and placed it in our tRestClient demo.
5.2 - The tRestClient DropBox project
Here is the main form:
Clicking on "get_token_" opens the redirection browser, and the steps are
detailed below with the Indy version of the Rest Client.
We then tried to use our token, by clicking "fetch_metadata_". The code is beautifully simple:
Procedure TForm1.fetch_metadata_Click(Sender: TObject);
Begin display('');
display('> fetch data'); ResetRESTComponentsToDefaults;
RESTClient.BaseURL := base_url_edit_.Text;
RESTClient.Authenticator := OAuth2_Dropbox;
OAuth2_Dropbox.AccessToken := f_dropbox_access_token;
RESTRequest.Resource := dropbox_resource_uri_edit_.Text;
RESTRequest.Params.AddItem('ROOT', 'dropbox',
TRESTRequestParameterKind.pkURLSEGMENT);
RESTRequest.Params.AddItem('PATH', '',
TRESTRequestParameterKind.pkURLSEGMENT);
RESTRequest.Execute; display('< fetch data');
End; // fetch_metadata_Click | However the result was a "400 bad request" with RestResponse.Content telling
"Unknown API function: "metadata/dropbox/".
I tried to download files or upload files, the first resulted in "Unexpected URL params: "access_token"", the second in an access violation. Spending some
time exploring the Delphi Rest architecture would have solved those problems. But I decided instead to use Indy which allows more direct control over the HTTP code.
6 - The Indy DropBox Rest Client 6.1 - Objective Indy was selected since it allows direct access to Headers and can display a full log of what was sent and received,
Of course, since, as I understand, the Delphi Rest framework (and the DataSnap framework) rest (pun intended) on the Indy Server. So whatever logging you can accomplish using the basic tIdHttpServer should be possible with the Delphi
Rest Framework. Finding out how to do so proves to be somewhat difficult to accomplish however.
6.2 - Getting the DropBox access token To get the access token, we use a WebBrowser to ask for the token. The URI
tells which redirect URL should be called. The OWNER accepts the token request and DropBox finally sends back the access token. The application looks like this
Here ie the procedure asking the access token: | we click the "get_access_token_direct_" button The code is the following
Procedure TForm1.get_access_token_direct_Click(Sender: TObject);
Const k_get_access_code_url=
'https://www.dropbox.com/1/oauth2/authorize'
+ '?client_id=%s'
+ '&response_type=%s'
+ '&redirect_uri=%s';
Var l_dropbox_authorize_url: string;
l_get_result: String; Begin
dropbox_access_token_edit_.Text:= '';
display('> get_token_direct_Click');
l_dropbox_authorize_url:= Format(k_get_access_code_url,
[tIdURI.PathEncode(k_client_id), tIdURI.PathEncode('token'),
k_redirection_uri]);
display('url '+ l_dropbox_authorize_url);
m_last_title:= ''; m_last_url:= '';
If Not execute_.Checked
Then Exit;
display_pagecontrol.ActivePage:= browser_tabheet_;
// -- this starts the Browser asking the access code
WebBrowser1.Navigate(l_dropbox_authorize_url);
display('< get_token_direct_Click'); End; // get_token_direct_Click
| The following URL will be built (new line added) https://www.dropbox.com/1/oauth2/authorize?
client_id=lyto17f3j3rr120 &response_type=token &redirect_uri=http://localhost:3000 | The WebBrowser will be started with this URL |
| DropBox redirects to our redirection URL and asks for the user / password. We enter our credentials
| | then we click "Sign in" |
| DropBox warns us |
| we accept the risk by clicking "Continue" | |
DropBox asks the OWNER whether he allows the "delphi_boxer_app" to receive the access token |
| we click "Allow" | | the application displays the access token in the
dropbox_"access_token_edit_ and switches back to the log tabsheet (aka "closes the redirection browser") |
We can follow the dialog since we create
- the OnTitleChange tWebBrowser event to see when we receive a page from DropBox
Procedure TForm1.WebBrowser1TitleChange(ASender: TObject;
Const Text: WideString); Begin
If (Text <> m_last_title)
Then Begin display_line;
display('new_title '+ Text);
m_last_title := Text; End;
End; // WebBrowser1TitleChange | - then NavigateComplete2 which will contains the access token
Procedure TForm1.WebBrowser1NavigateComplete2(ASender: TObject;
Const pDisp: IDispatch; Const URL: OleVariant);
Var l_access_token_position: integer;
l_access_token: string;
l_ansi_token: AnsiString; Begin
m_last_url := VarToStrDef(URL, '');
l_access_token_position := Pos('access_token=', m_last_url);
If l_access_token_position > 0
Then Begin
l_access_token := Copy(m_last_url, l_access_token_position + 13,
Length(m_last_url));
If (Pos('&', l_access_token) > 0) Then
l_access_token := Copy(l_access_token, 1, Pos('&', l_access_token) - 1);
End; End; // WebBrowser1NavigateComplete2 |
This the URL with our access token http://localhost:3000/ #access_token=X5SWPFqX3CAAAAAAAAAAi9X1yDmtz
e5yswCwjcGxUZGQd4t1I7ZLfyEe40o1iuMV &token_type=bearer &uid=2683171456 &account_id=dbid%3AAADj5fJIBLc0YhtQvvKqiMgnSoBfN-kXjFI |
So we simply look for the "access_token" keyword to extract this token, and save it for later reuse
And here is the log of the events
> get_token_direct_Click url https://www.dropbox.com/1/oauth2/authorize? ... < get_token_direct_Click new_title Dropbox - API Request Authorization - Sign in > browser.NavigateComplete2
https://www.dropbox.com/1/oauth2/authorize?client ... < browser.NavigateComplete2 > browser.NavigateComplete2 https://dropboxcaptcha.com/ < browser.NavigateComplete2 > browser.NavigateComplete2
https://www.google.com/recaptcha/api2/anchor?ar=1&k ... < browser.NavigateComplete2 > browser.NavigateComplete2 https://www.google.com/recaptcha/api2/anchor?ar=1&k=6 ... < browser.NavigateComplete2
> browser.NavigateComplete2 https://www.google.com/recaptcha/api2/anchor?ar=1&k=6 ... < browser.NavigateComplete2 > browser.NavigateComplete2 https://client-api.arkoselabs.com/v2/419899FA-7FAF-5C1 ...
< browser.NavigateComplete2 new_title https://www.dropbox.com/1/oauth2/authorize?client ... new_title API Request Authorization - Dropbox > browser.BeforeNavigate2 https://www.dropbox.com/1/oauth2/authorize_submit
< browser.BeforeNavigate2 > browser.NavigateComplete2 http://localhost:3000/#access_token=X5SWPFqX3CAAAAAAAA ... access_token X5SWPFqX3CAAAAAAAAAAoAtT-SxIHgdTOA-Y7 ... < browser.NavigateComplete2
|
6.3 - Get The Code then the Access Token You might think that this is somehow contrieved. Well, it is still possible to make it more complicated.
As I understand, we should rather first ask DropBox for a "code", and then asks DropBox to convert this "code" into the "access token". Here is how: - the request procedure for the code is
Procedure TForm1.get_code_Click(Sender: TObject);
Const k_get_url_2= 'https://www.dropbox.com/1/oauth2/authorize?'
+ 'client_id=%s' + '&response_type=code'
+ '&redirect_uri=%s';
Var l_get_code_url: String; Begin
display('> get_code');
l_get_code_url:= Format(k_get_url_2,
[k_client_id, tIdUri.PathEncode(k_redirection_uri)]);
display(l_get_code_url); m_last_title_2:= '';
m_last_url_2:= '';
ClipBoard.AsText:= 'drop.owner@free.fr';
display_pagecontrol.ActivePage:= code_brownser_tabsheet;
WebBrowser2.Navigate(l_get_code_url);
display('< get_code'); End; // get_code_Click |
- clicking on "get_code_" brings us to the same 3 DropBox pages, with at the end the OnNavigateComplete2 URL:
http://localhost:3000/? code=X5SWPFqX3CAAAAAAAAAApsdRO1Hs606iQFcTAkFRNuI | - then we click "get_token_from_code_"
Procedure TForm1.get_token_from_code_Click(Sender: TObject);
// -- requests the code // curl https://api.dropbox.com/oauth2/token \
// -d code=<AUTHORIZATION_CODE> \
// -d grant_type=authorization_code \
// -d redirect_uri=<REDIRECT_URI> \
// -u <APP_KEY>:<APP_SECRET>
Const k_get_access_token_from_code_url= 'https://api.dropboxapi.com/oauth2/token';
Var l_c_id_http: TIdHTTP;
l_json: String;
l_c_utf8_json_string_stream: tStringStream;
l_post_response: String; Begin
display('> get_token_from_code_Click');
l_c_id_http := TIdHTTP.Create(Nil);
With l_c_id_http Do Try
HandleRedirects := False;
OnRedirect:= handle_access_token_redirect;
ConnectTimeout := 1000; // FHTTPTimeout;
Request.BasicAuthentication := False;
IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(l_c_id_http);
Request.Accept := 'application/x-www-form-urlencoded';
Request.ContentType := 'application/json';
OnRedirect:= handle_access_from_token_redirect;
l_json:= '{ "code":"' + f_dropbox_code_token + '", '+ k_new_line
+ ' "grant_type":"authorization_code", '+ k_new_line
+ ' "redirect_uri":"'+ tIdUri.PathEncode(k_redirection_uri)+ '", '+ k_new_line
+ ' "client_id":"'+ k_client_id +'", '+ k_new_line
+ ' "client_secret":"'+ k_secret_id +'" }';
display_formatted_json(l_json);
l_c_utf8_json_string_stream := TStringStream.Create(l_json, TEncoding.UTF8, True);
Try display('--- send post');
l_post_response := l_c_id_http.Post(k_get_access_token_from_code_url,
l_c_utf8_json_string_stream);
display('--- post_result');
display(l_post_response); Finally
l_c_utf8_json_string_stream.Free; End;
Finally l_c_id_http.Free;
End; display('< get_token_from_code_Click');
End; // get_token_from_code_Click | - the Json is the following
{ "code":"3CAAAAAAAAAApsdRO1Hs606iQFcTAkFRNuI", "grant_type":"authorization_code",
"redirect_uri":"http://localhost:3000", "client_id":"lyto17f3j3rr120", "client_secret":"6u9c630qw7tgy72" } |
- at this stage we should have received the access token. Sadly we got the following exception
The Indy log is the following
Stat Connected. Sent 02/12/2019 08:33:55: POST /oauth2/token HTTP/1.0
Content-Type: application/json Content-Length: 201 Host: api.dropboxapi.com Accept: application/x-www-form-urlencoded
Accept-Encoding: identity User-Agent: Mozilla/3.0 (compatible; Indy Library) Sent 02/12/2019 08:33:55: { "code":"3CAAAAAAAAAApsdRO1Hs606iQFcTAkFRNuI",
"grant_type":"authorization_code", "redirect_uri":"http://localhost:3000", "client_id":"lyto17f3j3rr120", "client_secret":"6u9c630qw7tgy72" }
Recv 02/12/2019 08:33:55: HTTP/1.1 400 Bad Request Server: nginx Date: Mon, 02 Dec 2019 07:33:59 GMT Content-Type: application/json
Connection: close Content-Security-Policy: sandbox; frame-ancestors 'none' X-Dropbox-Request-Id: 3c6874c6aa49211bdad2692dbaf9c895
X-Frame-Options: DENY X-Content-Type-Options: nosniff Content-Disposition: attachment; filename='error'
{"error_description": "No auth function available for given request", "error": "invalid_request"} Stat Disconnected. |
I tried to Encode64 the client_id and the secret_id, but without success. Hopefully some reader will be kind enough to tell me where I goofed.
Fortunately, I already have the magic "access token", be it from my DropBox App
page, or from the "get_acess_token_direct" procedure. So now we can proceed with the standard DropBox file management routines: listing the files, downloading or uploading some file etc.
6.4 - Listing our DropBox files
The procedure to list the files is:
Procedure TForm1.list_dropbox_Click(Sender: TObject);
Const k_dropbox_list_folder_url =
'https://api.dropboxapi.com/2/files/list_folder';
Var l_c_id_http: TIdHTTP;
l_c_json_parameters: TStringStream;
l_post_result: String;
Stream: TMemoryStream; Begin
open_id_log;
l_c_json_parameters := TStringStream.Create('{ "path": "" }');
Try
l_c_id_http := TIdHTTP.Create(Nil);
Try
l_c_id_http.Intercept:= IdLogFile1;
l_c_id_http.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(l_c_id_http);
l_c_id_http.Request.CustomHeaders.Values['Authorization'] := 'Bearer '
+ f_dropbox_access_token;
l_c_id_http.Request.BasicAuthentication := False;
l_c_id_http.Request.ContentType := 'application/json';
l_post_result := l_c_id_http.Post(k_dropbox_list_folder_url,
l_c_json_parameters);
display(l_post_result); // -- format
display_formatted_json(l_post_result); Finally
l_c_id_http.Free; End;
Finally l_c_json_parameters.Free;
End; close_id_log; End; // list_dropbox_
|
The (truncated) Json response is { "entries":
[ { ".tag":"file",
"name":"Get Started with Dropbox Paper.url", "path_lower":"/get started with dropbox paper.url",
"path_display":"/Get Started with Dropbox Paper.url", "id":"id:cMv7L4TgJeAAAAAAAAAACQ",
"client_modified":"2019-11-09T14:42:20Z", "server_modified":"2019-11-09T14:42:20Z",
"rev":"01596eae7e0fcb0000000018a664b50", "size":240,
"is_downloadable":true, "content_hash":"f40c1228343d7e ... "
} , ... other entries ] ,
"cursor":"AAH6U94Nk_TC_OX2mG6rG86u9eh0BuQ ... ", "has_more":false } |
Of course we could also look at the Indy log to examine the full TCP / IP exchange.
6.5 - Downloading a file from DropBox Our Dropbox folder contains a "bbb.txt" file, and we want it to download it as
dropbox_download_test.txt in our .EXE folder Here is the code:
Procedure TForm1.download_Click(Sender: TObject);
// -- curl -X POST https://content.dropboxapi.com/2/files/download \ // -- --header "Authorization: Bearer
// -- --header "Dropbox-API-Arg: {\"path\": \"/Homework/math/Prime_Numbers.txt\"}"
Const k_URL = 'https://content.dropboxapi.com/2/files/download';
Var l_to_download_full_file_name: String;
l_c_file_stream: TFileStream;
l_post_result: String; l_c_id_http: tIdHttp;
Begin open_id_log;
l_c_id_http:= tIdHttp.Create(Nil);
With l_c_id_http Do Begin
HandleRedirects := True;
IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(l_c_id_http);
Request.BasicAuthentication := False;
Request.CustomHeaders.Values['Authorization'] := 'Bearer '
+ f_dropbox_access_token;
Request.CustomHeaders.Values['Dropbox-API-Arg'] :=
Format('{ "path": "%s"}', ['/bbb.txt']);
Request.ContentType := 'application/octet-stream';
// Request.ContentType := '';
display(Request.CustomHeaders.Text);
If Not execute_.Checked
Then Exit;
l_to_download_full_file_name:= f_exe_path+ 'dropbox_download_test.txt';
l_c_file_stream := TFileStream.Create(l_to_download_full_file_name, fmCreate);
Try
l_post_result := Post(k_URL, l_c_file_stream);
display(Response.ResponseText);
display(IntToStr(Response.ResponseCode));
Finally l_c_file_stream.Free;
End;
save_string(l_post_result, l_to_download_full_file_name);
End; // with l_c_id_http l_c_id_http.Free;
close_id_log; End; // download_Click |
This took me some time to understand. I first believed that the file would be saved by the tFileStream. Yet the result was always a 0 byte file. It turns out that the file is resurned as the tIdHttp.Post function. Therefore we saved
this string to disk.
6.6 - Upload a file to DropBox We placed some dropbox_upload_test.txt file in our .EXE folder, and want to upload it to our DropBox folder as ddd.txt This is our procedure :
Procedure TForm1.upload_Click(Sender: TObject);
Const k_URL = 'https://content.dropboxapi.com/2/files/upload';
Var l_to_upload_full_file_name: String;
l_c_file_stream: TFileStream;
l_post_result: String; l_c_id_http: tIdHttp;
Begin open_id_log;
l_c_id_http:= tIdHttp.Create(Nil);
With l_c_id_http Do Begin
HandleRedirects := True;
IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(l_c_id_http);
Request.BasicAuthentication := False;
Request.CustomHeaders.Values['Authorization'] := 'Bearer '
+ f_dropbox_access_token;
Request.CustomHeaders.Values['Dropbox-API-Arg'] :=
Format('{ "path": "%s", "mode": "overwrite"}',
['/ddd.txt']);
Request.ContentType := 'application/octet-stream';
l_to_upload_full_file_name:= f_exe_path+ 'dropbox_upload_test.txt';
If Not FileExists(l_to_upload_full_file_name)
Then display_bug_stop('not_found '+ l_to_upload_full_file_name);
l_c_file_stream := TFileStream.Create(l_to_upload_full_file_name, fmOpenRead);
Try
l_post_result := Post(k_URL, l_c_file_stream);
display(l_post_result); Finally
l_c_file_stream.Free; End;
End; // with l_c_id_http l_c_id_http.Free;
close_id_log; End; // post_dropbox_Click |
7 - Comments 7.1 - DropBox - in the DropBox credential page, there si a suggestion "Sign in with Google". I recommend NOT TO DO SO. To avoid the typing of the user / password, I did
it, and then Google warned me, on the PC and on my phone, that DropBox had full access to my google account. So I changed my Google password and no longer used this option.
- the "Url Endpoints" are not very consistent. Sometimes www.dropbox.com. But other times api.dropboxapi.com. The best is to look at the DropBox documentation
- to write our DropBox App, we used a localhost IP. In this case, and this
case only, your redirection URL can be HTTP. In production applications, it must be HTTPS.
- we created the drop.owner@free.fr mail. Then I wanted to call the App "dropbox_xxx". However DropBox forbids it. The name MUST not contain
"dropbox". This is why we called our App "delphi_boxer_app"
- beware that DropBox now uses a Version 2 api, so most (not all) URIs use the "/2/" segment instead of the old "/1/" segment (which you still may
encounter in some of the examples downloaded from Google
- during my Google Oauth2 journeys, I came across some posts claiming that DropBox was not "totally Oauth2". Whatever that means ...
7.2 - Our Indy Implementation - to avoid typing the credentials, in my trials, before calling the tWebBrowser.Navigate I used
ClipBoard.AsText:= 'drop.owner@free.fr'; | When the Browser page was presented, I simply pasted the email in the page
email edit. Then I clicked the "pass" button which copies my password to the clipboard and pasted that to the Page password edit. Of course hardoding the user / password in the code is not something one
would recommend. In fact it runs against the basic principle of Oauth2, where the application does never see the user / pass and uses this convoluted redirection scheme to let the OWNER type his credentials.
- about the extraction of the access token or the code, we used the tWebBrowser.OnAfterExecute2 event. The Delphi RestDemo calls a Tfrm_OAuthWebForm which contains the tWebBrowser. This Form is located in
sources\data\rest\REST.Authenticator.OAuth.WebForm.Win.pas The WebBrowser implements the onTitleChange, OnBeforeExecute2 and onAfterExecute2 events. Those events can call our application callbacks to
extract whatever information we want from the different Rest Servers. In our case, DropBox uses the onAfterExecute2 event to send the access token or the code back, but I imagine some other Rest Servers have other techniques.
In the callbacks, once the relevant information has been extracted (the access token, for instance), the callback uses the VAR pv_do_close_the_browser parameter which is used by tWebBrowser event to close the WebBrowser.
It seems that closing the redirected page was a major issue in the Oauth2 implementation, because the user would have this Browser page still open after having answered to all the questions.
In our Indy implementation, we simply change the Tabsheet page once we got the DropBox answer. And we used the OnTitleChange to display the page title in an Edit at the
top of the TabSheet, as well as in the log (to be able to follow the action) - we implemented some kind of "access token cache". We did not want to hardcode the access token in the tEdit .DFM. Therefore, whenever we request
and get a new access token, we save it in a file, and this file is then loaded at the start of the next execution. This avoids to hardcode the access token (which changes after a couple of hours).
- to finalize our Indy version, StackOverflow, as usual, was our best friend, and Remy LEBEAU posts helped a lot.
7.3 - Oauth2 - I wanted to understand Oauth2 because a customer started talking about "Rest
Services Security". At that time, I figured that Oauth2 would be an improved version of Oauth1. I now understand that this seems not to be the case, and the creator of Oauth2 had jumped ship.
Anyway, the customer decided to use HTTPS and this saved me the trouble to create a Oauth2 SERVER. - for simplicity, we chose to develop the whole thing on the same PC. DropBox
allows, "for debugging", the use of a "localhost" redirection URL. This is the only case where HTTP is accepted. For production, the redirection URL MUST be HTTPs.
But the problem is more serious: being at the same time the OWNER and the
CLIENT on the same machine somehow obscures the working of the OAuth2 protocol : who is doing what and where.
7.4 - Delphi Components - if you seriously want to implement DropBox, you will find a couple of Delphi
Components using Google.
TMS also sells a Cloud Pack which includes (along with Google Calendar, Mail, Contacts etc, Microsoft One
Drive, Live Calendars, Outlook Mail, FaceBook, Twitter, Linkedin, PayPal) a DropBox Rest Client - since I mentioned SSL, I had to create Auto Signed certificates. Using the
command line (or a Delphi program with CreateProcess) was quite tedious. I then came across the ICS SSL upgrade. François PIETTE and his team have done a wonderful job there with 22 demos, including the OverbyteIcsPemTool.dpr
which creates the self signed certificate with one clic. Of course, for production we will use Let's Encrypt free certificates, or rather some paying certificates.
But among the 22 demos, you have HTTPS, JOSE ( Json Object Signing), POP3
and SMTP SSL, FTP SSL etc. - aside from Delphi and ICS, there are also MANY REST Frameworks available for Delphi: Mars Curiosity, Delphi MVC, Mormot, Habari etc. So far I located over 12 of those ...
8 - Download the Sources Here are the source code files:
You also must add the SSL libraries from Indy Fulgan SSL DLLs The .ZIP file(s) contain: - the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
- any .TXT for parameters, samples, test data
- all units (.PAS) for units
Those .ZIP - are self-contained: you will not need any other product (unless expressly mentioned).
- for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
- will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
- create or select any folder of your choice
- unzip the downloaded file
- using Delphi, compile and execute
To remove the .ZIP simply delete the folder.
The Pascal code uses the Alsacian notation, which prefixes identifier by program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lass etc. This notation is presented in the Alsacian Notation paper. The .ZIP file(s) contain:
- the main program (.DPROJ, .DPR, .RES), the main form (.PAS, .ASPX), and any other auxiliary form or files
- any .TXT for parameters, samples, test data
- all units (.PAS .ASPX and other) for units
Those .ZIP
- are self-contained: you will not need any other product (unless expressly mentioned).
- will not modify your PC in any way beyond the path where you placed the .ZIP
(no registry changes, no path outside from the container path creation etc).
To use the .ZIP: - create or select any folder of your choice.
- unzip the downloaded file
- using Delphi, compile and execute
To remove the .ZIP simply delete the folder. The Pascal code uses the Alsacian notation, which prefixes identifier by program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre,
F_unction, C_lass etc. This notation is presented in the Alsacian Notation paper.
As usual:
- please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will
be helpful for other readers
- we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
- or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
- and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in
your blog or newsgroup posts when relevant. That's the way we operate: the more traffic and Google references we get, the more articles we will write.
9 - References and Links
- Rest Services Directory find the documentation about more than 20.000 web services
- DropBox:
- Delphi did include DropBox in the RestDemo sample. This sample is
included in the Delphi distribution, or available on SourceForge
- concerning Rest Web Services, we must pay our tribute to Marco CANTU who was the first to promote REST into the Delphi World. I still have articles
dating back to 2009 (I archive everything, fearing the site might vanish). Still interesting is REST in Delphi 2010. Plus
the many other posts, webminars, conferences and Youtube videos
- TMS also sells a Cloud Pack which includes (along with Google Calendar, Mail, Contacts etc, Microsoft One
Drive, Live Calendars, Outlook Mail, FaceBook, Twitter, Linkedin, PayPal) a DropBox Rest Client
- FAQ Using tRestOAuth : the OAuth faq of Overbyte with the many OpenSSL additions
And, finally - we are doing Delphi custom
developments. Right now we are finishing a Rest Server and a test Rest Client for a customer to export invoices and purchase orders.
- Delphi oAuth2
Tutorial Video : Jim McKeeth asked us to produce a 10 minute video for CodeRage 2019. We chose the oAuth2 subject. This video was constrainted by Embardacero to less than 10 minutes, so it is quite
short, and in my optinion, is less detailed than this paper. But for those who prefer the image to the text ...
10 - The author Felix John COLIBRI works at the Pascal
Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly
active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi
Xe_n migrations, refactoring), Delphi Consulting and Delph
training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP / UML, Design Patterns, Unit Testing training sessions. |